﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Utilities;

namespace SPHelper
{
	public class Caml
	{
		public class OrderByField
		{
			public string FieldName { get; set; }
			public bool IsDescending { get; set; }
			public OrderByField() { }
			public OrderByField(string fieldname, bool isDescending)
			{
				FieldName = fieldname;
				IsDescending = isDescending;
			}
			public OrderByField(string fieldname)
			{
				FieldName = fieldname;
				IsDescending = false;
			}
		}

		/// <summary>
		/// pass order by fields, convert into CAML query text, includes OrderBy tags if needed
		/// </summary>
		/// <param name="orderByFields"></param>
		/// <returns></returns>
		public static string FormatOrderBy(List<OrderByField> orderByFields, bool isAddOrderByTags)
		{
			string o = null;
			if (orderByFields != null && orderByFields.Count > 0)
			{
				foreach (Caml.OrderByField f in orderByFields)
				{
					string ascendingValue = f.IsDescending ? "FALSE" : "TRUE";
					if (!string.IsNullOrEmpty(f.FieldName))
					{
						o += "<FieldRef Name='" + f.FieldName + "' Ascending='" + ascendingValue + "' />";
					}
				}
				if (isAddOrderByTags && !string.IsNullOrEmpty(o))
				{
					o = "<OrderBy>" + o + "</OrderBy>";
				}
			}
			return o;
		}

		/// <summary>
		/// pass order by fields, convert into CAML query text, includes OrderBy tags
		/// </summary>
		/// <param name="orderByFields"></param>
		/// <returns></returns>
		public static string FormatOrderBy(List<OrderByField> orderByFields)
		{
			return FormatOrderBy(orderByFields, true);
		}

		/// <summary>
		/// Get the orderby fields from a CAML query passed
		/// </summary>
		/// <param name="query"></param>
		/// <returns></returns>
		public static string[] GetOrderByFieldsFromQuery(string query)
		{
			string[] fields = null;
			try
			{
				// init
				List<string> _fields = new List<string>();


				// queryText += "<OrderBy><FieldRef Name='PublicationDate' Ascending='FALSE' /></OrderBy>";
				if (!string.IsNullOrEmpty(query) && query.ToLower().Contains("orderby"))
				{
					// provide root node if needed
					if (!query.ToLower().Contains("<query")) { query = "<Query>" + query + "</Query>"; }


					// 1.) get XML object
					System.Xml.XmlDataDocument xml = new System.Xml.XmlDataDocument();
					xml.LoadXml(query);

					// 2.) get only the orderby node
					System.Xml.XmlNode orderBy = xml.SelectSingleNode("Query/OrderBy");
					if (orderBy == null) { orderBy = xml.SelectSingleNode("OrderBy"); }
					if (orderBy == null) { throw new Exception("Failed to get orderby node in CAML query"); }

					// 3.) enum child nodes and get fields
					System.Xml.XmlNodeList fieldRefs = orderBy.ChildNodes;
					if (fieldRefs == null || fieldRefs.Count == 0)
					{ throw new Exception("OrderBy node has no child elements"); }
					foreach (System.Xml.XmlNode f in fieldRefs)
					{
						if (f.Attributes["Name"] != null && !string.IsNullOrEmpty(f.Attributes["Name"].Value))
						{
							_fields.Add(f.Attributes["Name"].Value);
						}
						else
						{
							SPHelper.Log.WriteError("efadtrsrcxfzvest", "Unknown child node in orderby node: " + f.InnerXml);
						}
					}

					// convert list to array
					if (_fields != null && _fields.Count > 0) { fields = _fields.ToArray(); }
				}
			}
			catch (Exception ex)
			{
				SPHelper.Log.WriteError("ghkfygudrese4sr57e68tr6t", ex, "query: " + query);
				fields = null;
			}
			return fields;
		}

		/// <summary>
		/// Convert date time into CAML readable date/time value
		/// </summary>
		/// <param name="dateTime"></param>
		/// <returns></returns>
		public static string FormatDateTime(DateTime dateTime)
		{
			return SPUtility.CreateISO8601DateTimeFromSystemDateTime(dateTime);
		}

		/// <summary>
		/// format a view field for an SPQuery, 
		/// for query.ViewFields = FormatSPQueryViewFields(string[]);
		/// These are the fields to return by SPQuery
		/// </summary>
		/// <param name="fields"></param>
		/// <returns></returns>
		public static string FormatSPQueryViewFields(string[] fields)
		{
			string o = null;
			if (fields != null && fields.Length > 0)
			{
				foreach (string field in fields)
				{
					if (!string.IsNullOrEmpty(field) && field.Trim() != string.Empty) 
					{ o += "<FieldRef Name='" + field + "' />"; }
				}
			}
			return o;
		}


		public enum Operator
		{
			And,
			Or,
		}
		public class Predicate
		{
			public string PredicateStatement { get; set; }
			public Operator Operator { get; set; }
			/// <summary>
			/// CAML Predicate clause with operator
			/// </summary>
			/// <param name="predicateStatement"></param>
			/// <param name="operatorForNextPredicate">Operator for this predicate and the following one. 
			/// If last predicate, then this operator is ignored!</param>
			public Predicate(string predicateStatement, Operator operatorForNextPredicate)
			{
				PredicateStatement = predicateStatement;
				Operator = operatorForNextPredicate;
			}

			/// <summary>
			/// CAML Predicate clause (uses AND operator)
			/// </summary>
			/// <param name="predicateStatement"></param>
			public Predicate(string predicateStatement)
			{
				PredicateStatement = predicateStatement;
				Operator = Caml.Operator.And;
			}
		}

		/// <summary>
		/// Takes collection of predicates and converts them into nest CAML predicates with AND/OR operators added
		/// </summary>
		/// <param name="predicates"></param>
		/// <param name="isAddWhereTags"></param>
		/// <returns></returns>
		public static string FormatPredicates(List<Predicate> predicates, bool isAddWhereTags)
		{
			string o = null;
			if (predicates != null && predicates.Count > 0)
			{
				// delete empty predicates
				for (int i = 0; i < predicates.Count; i++)
				{
					if (string.IsNullOrEmpty(predicates[i].PredicateStatement)
						|| predicates[i].PredicateStatement.Trim() == string.Empty
						)
					{
						predicates.RemoveAt(i);
						i--;
					}
				}

				// list of open operators that still need to be closed
				var openOperators = new List<Operator>();

				// merge predicates
				foreach (Predicate p in predicates)
				{
					// opening operator
					// only add if: 1.) not the only predicate, 2.) not the last predicate
					if (predicates.Count > 1 && predicates[predicates.Count - 1] != p)
					{
						// adding a predicate where there are more predicates, and not the last predicate
						o += string.Format("<{0}>", p.Operator.ToString());
						openOperators.Add(p.Operator);
					}

					// predicate statement
					o += p.PredicateStatement;
				}

				// close operators (in reverse order)
				if (openOperators != null && openOperators.Count > 0)
				{
					for (int i = openOperators.Count - 1; i >= 0; i--)
					{
						o += string.Format("</{0}>", openOperators[i].ToString());
					}
				}

				if (isAddWhereTags && !string.IsNullOrEmpty(o)) { o = "<Where>" + o + "</Where>"; }
			}
			return o;
		}

		/// <summary>
		/// Takes collection of predicates and converts them into nest CAML predicates with AND/OR operators added
		/// Adds Where tags
		/// </summary>
		/// <param name="predicates"></param>
		/// <returns></returns>
		public static string FormatPredicates(List<Predicate> predicates)
		{
			return FormatPredicates(predicates, true);
		}
	}
}
